home *** CD-ROM | disk | FTP | other *** search
/ Amiga Plus Leser 15 / Amiga Plus Leser CD 15.iso / Tools / Development / MosaicSRC / libwww2 / HTNews.c < prev    next >
Encoding:
C/C++ Source or Header  |  2002-03-13  |  23.2 KB  |  912 lines

  1. /*            NEWS ACCESS                HTNews.c
  2. **            ===========
  3. **
  4. ** History:
  5. **    26 Sep 90    Written TBL
  6. **    29 Nov 91    Downgraded to C, for portable implementation.
  7. */
  8. /* Implements:
  9. */
  10. #include "HTNews.h"
  11.  
  12. #define NEWS_PORT 119        /* See rfc977 */
  13. #define APPEND            /* Use append methods */
  14. #define MAX_CHUNK    40    /* Largest number of articles in one window */
  15. #define CHUNK_SIZE    20    /* Number of articles for quick display */
  16.  
  17. #ifndef DEFAULT_NEWS_HOST
  18. #define DEFAULT_NEWS_HOST "news"
  19. #endif
  20. #ifndef SERVER_FILE
  21. #define SERVER_FILE "/usr/local/lib/rn/server"
  22. #endif
  23.  
  24. #define FAST_THRESHOLD 100    /* Above this, read IDs fast */
  25. #define CHOP_THRESHOLD 50    /* Above this, chop off the rest */
  26.  
  27. #include <ctype.h>
  28. #include "HTUtils.h"            /* Coding convention macros */
  29. #include "tcp.h"
  30.  
  31. #include "HTAlert.h"
  32. #include "HTML.h"
  33. #include "HTParse.h"
  34. #include "HTFormat.h"
  35. #include "HTTCP.h"
  36.  
  37. struct _HTStructured
  38. {
  39.   CONST HTStructuredClass *    isa;
  40.   /* ... */
  41. };
  42.  
  43. /* #define TRACE 1 */
  44.  
  45. #define LINE_LENGTH 512         /* Maximum length of line of ARTICLE etc */
  46. #define GROUP_NAME_LENGTH    256    /* Maximum length of group name */
  47.  
  48.  
  49. /*    Module-wide variables
  50. */
  51. PUBLIC char * HTNewsHost;
  52. PRIVATE int s;                    /* Socket for NewsHost */
  53. PRIVATE char response_text[LINE_LENGTH+1];    /* Last response */
  54. /* PRIVATE HText *    HT;    */        /* the new hypertext */
  55. PRIVATE HTStructured * target;            /* The output sink */
  56. PRIVATE HTStructuredClass targetClass;        /* Copy of fn addresses */
  57. PRIVATE HTParentAnchor *node_anchor;        /* Its anchor */
  58. PRIVATE int    diagnostic;            /* level: 0=none 2=source */
  59.  
  60.  
  61. #define PUTC(c) (*targetClass.put_character)(target, c)
  62. #define PUTS(s) (*targetClass.put_string)(target, s)
  63. #define START(e) (*targetClass.start_element)(target, e, 0, 0)
  64. #define END(e) (*targetClass.end_element)(target, e)
  65.  
  66. PUBLIC CONST char * HTGetNewsHost NOARGS
  67. {
  68.     return HTNewsHost;
  69. }
  70.  
  71. PUBLIC void HTSetNewsHost ARGS1(CONST char *, value)
  72. {
  73.     StrAllocCopy(HTNewsHost, value);
  74. }
  75.  
  76. /*    Initialisation for this module
  77. **    ------------------------------
  78. **
  79. **    We pick up the NewsHost name from
  80. **
  81. **    1.    Environment variable NNTPSERVER
  82. **    2.    File SERVER_FILE
  83. **    3.    Compilation time macro DEFAULT_NEWS_HOST
  84. **    4.    Default to "news"
  85. */
  86. PRIVATE BOOL initialized = NO;
  87. PRIVATE BOOL initialize NOARGS
  88. {
  89.   /*   Get name of Host
  90.    */
  91.   if (getenv("NNTPSERVER"))
  92.     {
  93.       StrAllocCopy(HTNewsHost, (char *)getenv("NNTPSERVER"));
  94.       if (TRACE) fprintf(stderr, "HTNews: NNTPSERVER defined as `%s'\n",
  95.              HTNewsHost);
  96.     }
  97.   else
  98.     {
  99.       char server_name[256];
  100.       FILE* fp = fopen(SERVER_FILE, "r");
  101.       if (fp)
  102.     {
  103.       if (fscanf(fp, "%s", server_name)==1)
  104.         {
  105.           StrAllocCopy(HTNewsHost, server_name);
  106.           if (TRACE) fprintf(stderr,
  107.                  "HTNews: File %s defines news host as `%s'\n",
  108.                  SERVER_FILE, HTNewsHost);
  109.         }
  110.       fclose(fp);
  111.     }
  112.     }
  113.   if (!HTNewsHost)
  114.     HTNewsHost = DEFAULT_NEWS_HOST;
  115.  
  116.   s = -1;        /* Disconnected */
  117.  
  118.   return YES;
  119. }
  120.  
  121.  
  122.  
  123. /*    Send NNTP Command line to remote host & Check Response
  124. **    ------------------------------------------------------
  125. **
  126. ** On entry,
  127. **    command points to the command to be sent, including CRLF, or is null
  128. **        pointer if no command to be sent.
  129. ** On exit,
  130. **    Negative status indicates transmission error, socket closed.
  131. **    Positive status is an NNTP status.
  132. */
  133.  
  134.  
  135. PRIVATE int response ARGS1(CONST char *,command)
  136. {
  137.   int result;
  138.   char * p = response_text;
  139.   if (command)
  140.     {
  141.       int status;
  142.       int length = strlen(command);
  143.       if (TRACE)
  144.     fprintf(stderr, "NNTP command to be sent: %s", command);
  145.       status = NETWRITE(s, command, length);
  146.       if (status<0)
  147.     {
  148.       if (TRACE) fprintf(stderr,
  149.                  "HTNews: Unable to send command. Disconnecting.\n");
  150.       NETCLOSE(s);
  151.       s = -1;
  152.       return status;
  153.     } /* if bad status */
  154.     } /* if command to be sent */
  155.  
  156.   for(;;)
  157.     {
  158.       if (((*p++=HTGetCharacter ()) == LF)
  159.       || (p == &response_text[LINE_LENGTH]))
  160.     {
  161.       *p++=0;                /* Terminate the string */
  162.       if (TRACE) fprintf(stderr, "NNTP Response: %s\n", response_text);
  163.       sscanf(response_text, "%d", &result);
  164.       return result;
  165.     } /* if end of line */
  166.  
  167.       if (*(p-1) < 0)
  168.     {
  169.       if (TRACE) fprintf(stderr,
  170.                  "HTNews: EOF on read, closing socket %d\n", s);
  171.       NETCLOSE(s);    /* End of file, close socket */
  172.       return s = -1;    /* End of file on response */
  173.     }
  174.     } /* Loop over characters */
  175. }
  176.  
  177.  
  178. /*    Case insensitive string comparisons
  179. **    -----------------------------------
  180. **
  181. ** On entry,
  182. **    template must be already un upper case.
  183. **    unknown may be in upper or lower or mixed case to match.
  184. */
  185. PRIVATE BOOL match ARGS2 (CONST char *,unknown, CONST char *,template)
  186. {
  187.     CONST char * u = unknown;
  188.     CONST char * t = template;
  189.     for (;*u && *t && (TOUPPER(*u)==*t); u++, t++) /* Find mismatch or end */ ;
  190.     return (BOOL)(*t==0);        /* OK if end of template */
  191. }
  192.  
  193. /*    Find Author's name in mail address
  194. **    ----------------------------------
  195. **
  196. ** On exit,
  197. **    THE EMAIL ADDRESS IS CORRUPTED
  198. **
  199. ** For example, returns "Tim Berners-Lee" if given any of
  200. **    " Tim Berners-Lee <tim@online.cern.ch> "
  201. **  or    " tim@online.cern.ch ( Tim Berners-Lee ) "
  202. */
  203. PRIVATE char * author_name ARGS1 (char *,email)
  204. {
  205.     char *s, *e;
  206.  
  207.     if ((s=strchr(email,'(')) && (e=strchr(email, ')')))
  208.     if (e>s) {
  209.         *e=0;            /* Chop off everything after the ')'  */
  210.         return HTStrip(s+1);    /* Remove leading and trailing spaces */
  211.     }
  212.  
  213.     if ((s=strchr(email,'<')) && (e=strchr(email, '>')))
  214.     if (e>s) {
  215.         strcpy(s, e+1);        /* Remove <...> */
  216.         return HTStrip(email);    /* Remove leading and trailing spaces */
  217.     }
  218.  
  219.     return HTStrip(email);        /* Default to the whole thing */
  220.  
  221. }
  222.  
  223. /*    Start anchor element
  224. **    --------------------
  225. */
  226. PRIVATE void start_anchor ARGS1(CONST char *,  href)
  227. {
  228.   PUTS ("<A HREF=\"");
  229.   PUTS (href);
  230.   PUTS ("\">");
  231. }
  232.  
  233. /*    Paste in an Anchor
  234. **    ------------------
  235. **
  236. **
  237. ** On entry,
  238. **    HT    has a selection of zero length at the end.
  239. **    text    points to the text to be put into the file, 0 terminated.
  240. **    addr    points to the hypertext refernce address,
  241. **        terminated by white space, comma, NULL or '>'
  242. */
  243. PRIVATE void write_anchor ARGS2(CONST char *,text, CONST char *,addr)
  244. {
  245.     char href[LINE_LENGTH+1];
  246.  
  247.     {
  248.     CONST char * p;
  249.     strcpy(href,"news:");
  250.     for(p=addr; *p && (*p!='>') && !WHITE(*p) && (*p!=','); p++);
  251.     strncat(href, addr, p-addr);    /* Make complete hypertext reference */
  252.     }
  253.  
  254.     start_anchor(href);
  255.     PUTS(text);
  256.     PUTS("</A>");
  257. }
  258.  
  259.  
  260. /*    Write list of anchors
  261. **    ---------------------
  262. **
  263. **    We take a pointer to a list of objects, and write out each,
  264. **    generating an anchor for each.
  265. **
  266. ** On entry,
  267. **    HT    has a selection of zero length at the end.
  268. **    text    points to a comma or space separated list of addresses.
  269. ** On exit,
  270. **    *text    is NOT any more chopped up into substrings.
  271. */
  272. PRIVATE void write_anchors ARGS1 (char *,text)
  273. {
  274.     char * start = text;
  275.     char * end;
  276.     char c;
  277.     for (;;) {
  278.     for(;*start && (WHITE(*start)); start++);  /* Find start */
  279.     if (!*start) return;            /* (Done) */
  280.     for(end=start; *end && (*end!=' ') && (*end!=','); end++);/* Find end */
  281.     if (*end) end++;    /* Include comma or space but not NULL */
  282.     c = *end;
  283.     *end = 0;
  284.     write_anchor(start, start);
  285.     *end = c;
  286.     start = end;            /* Point to next one */
  287.     }
  288. }
  289.  
  290. /*    Abort the connection                    abort_socket
  291. **    --------------------
  292. */
  293. PRIVATE void abort_socket NOARGS
  294. {
  295.     if (TRACE) fprintf(stderr,
  296.         "HTNews: EOF on read, closing socket %d\n", s);
  297.     NETCLOSE(s);    /* End of file, close socket */
  298.     PUTS("Network Error: connection lost");
  299.     PUTC('\n');
  300.     s = -1;        /* End of file on response */
  301.     return;
  302. }
  303.  
  304. /*    Read in an Article                    read_article
  305. **    ------------------
  306. **
  307. **
  308. **    Note the termination condition of a single dot on a line by itself.
  309. **    RFC 977 specifies that the line "folding" of RFC850 is not used, so we
  310. **    do not handle it here.
  311. **
  312. ** On entry,
  313. **    s    Global socket number is OK
  314. **    HT    Global hypertext object is ready for appending text
  315. */
  316. PRIVATE void read_article NOARGS
  317. {
  318.  
  319.     char line[LINE_LENGTH+1];
  320.     char *references=NULL;            /* Hrefs for other articles */
  321.     char *newsgroups=NULL;            /* Newsgroups list */
  322.     char *p = line;
  323.     BOOL done = NO;
  324.  
  325. /*    Read in the HEADer of the article:
  326. **
  327. **    The header fields are either ignored, or formatted and put into the
  328. **     Text.
  329. */
  330.     if (!diagnostic) {
  331.     (*targetClass.start_element)(target, HTML_ADDRESS, 0, 0);
  332.     while(!done){
  333.         char ch = *p++ = HTGetCharacter ();
  334.         if (ch==(char)EOF) {
  335.         abort_socket(); /* End of file, close socket */
  336.         return;     /* End of file on response */
  337.         }
  338.         if ((ch == LF) || (p == &line[LINE_LENGTH])) {
  339.         *--p=0;             /* Terminate the string */
  340.         if (TRACE) fprintf(stderr, "H %s\n", line);
  341.  
  342.         if (line[0]=='.') {
  343.             if (line[1]<' ') {          /* End of article? */
  344.             done = YES;
  345.             break;
  346.             }
  347.  
  348.         } else if (line[0]<' ') {
  349.             break;        /* End of Header? */
  350.         } else if (match(line, "SUBJECT:")) {
  351.             END(HTML_ADDRESS);
  352.             START(HTML_TITLE);            /** Uuugh! @@@ */
  353.             PUTS(line+8);
  354.             END(HTML_TITLE);
  355.             START(HTML_ADDRESS);
  356.             (*targetClass.start_element)(target, HTML_H1 , 0, 0);
  357.             PUTS(line+8);
  358.             (*targetClass.end_element)(target, HTML_H1);
  359.             END(HTML_ADDRESS);
  360.             (*targetClass.start_element)(target, HTML_ADDRESS , 0, 0);
  361.         } else if (match(line, "DATE:")
  362.             || match(line, "FROM:")
  363.             || match(line, "ORGANIZATION:")) {
  364.             strcat(line, "\n");
  365.             PUTS(strchr(line,':')+1);
  366.         } else if (match(line, "NEWSGROUPS:")) {
  367.             StrAllocCopy(newsgroups, HTStrip(strchr(line,':')+1));
  368.  
  369.         } else if (match(line, "REFERENCES:")) {
  370.             StrAllocCopy(references, HTStrip(strchr(line,':')+1));
  371.  
  372.         } /* end if match */
  373.         p = line;            /* Restart at beginning */
  374.         } /* if end of line */
  375.     } /* Loop over characters */
  376.     (*targetClass.end_element)(target, HTML_ADDRESS);
  377.  
  378.     if (newsgroups || references) {
  379.         (*targetClass.start_element)(target, HTML_DL , 0, 0);
  380.         if (newsgroups) {
  381.         (*targetClass.start_element)(target, HTML_DT , 0, 0);
  382.         PUTS("Newsgroups:");
  383.         (*targetClass.start_element)(target, HTML_DD , 0, 0);
  384.         write_anchors(newsgroups);
  385.         free(newsgroups);
  386.         }
  387.  
  388.         if (references) {
  389.         (*targetClass.start_element)(target, HTML_DT , 0, 0);
  390.         PUTS("References:");
  391.         (*targetClass.start_element)(target, HTML_DD , 0, 0);
  392.         write_anchors(references);
  393.         free(references);
  394.         }
  395.         (*targetClass.end_element)(target, HTML_DL);
  396.     }
  397.     PUTS("\n\n\n");
  398.  
  399.     }
  400.  
  401. /*    Read in the BODY of the Article:
  402. */
  403.     (*targetClass.start_element)(target, HTML_PRE , 0, 0);
  404.  
  405.     p = line;
  406.     while(!done){
  407.     char ch = *p++ = HTGetCharacter ();
  408.     if (ch==(char)EOF) {
  409.         abort_socket();    /* End of file, close socket */
  410.         return;        /* End of file on response */
  411.     }
  412.     if ((ch == LF) || (p == &line[LINE_LENGTH])) {
  413.         *p++=0;                /* Terminate the string */
  414.         if (TRACE) fprintf(stderr, "B %s", line);
  415.         if (line[0]=='.') {
  416.         if (line[1]<' ') {              /* End of article? */
  417.             done = YES;
  418.             break;
  419.         } else {            /* Line starts with dot */
  420.             PUTS(&line[1]);    /* Ignore first dot */
  421.         }
  422.         } else {
  423.  
  424. /*    Normal lines are scanned for buried references to other articles.
  425. **    Unfortunately, it will pick up mail addresses as well!
  426. */
  427.         char *l = line;
  428.         char * p;
  429.         while (p=strchr(l, '<')) {
  430.             char *q  = strchr(p,'>');
  431.             char *at = strchr(p, '@');
  432.             if (q && at && at<q) {
  433.             char c = q[1];
  434.             q[1] = 0;        /* chop up */
  435.             *p = 0;
  436.             PUTS(l);
  437.             *p = '<';               /* again */
  438.             *q = 0;
  439.             start_anchor(p+1);
  440.             *q = '>';               /* again */
  441.             PUTS(p);
  442.             PUTS("</A>");
  443.             q[1] = c;        /* again */
  444.             l=q+1;
  445.             } else break;        /* line has unmatched <> */
  446.         }
  447.         PUTS( l);    /* Last bit of the line */
  448.         } /* if not dot */
  449.         p = line;                /* Restart at beginning */
  450.     } /* if end of line */
  451.     } /* Loop over characters */
  452.  
  453.     (*targetClass.end_element)(target, HTML_PRE);
  454. }
  455.  
  456.  
  457. /*    Read in a List of Newsgroups
  458. **    ----------------------------
  459. */
  460. /*
  461. **    Note the termination condition of a single dot on a line by itself.
  462. **    RFC 977 specifies that the line "folding" of RFC850 is not used, so we
  463. **    do not handle it here.
  464. */
  465. PRIVATE void read_list NOARGS
  466. {
  467.  
  468.     char line[LINE_LENGTH+1];
  469.     char *p;
  470.     BOOL done = NO;
  471.  
  472. /*    Read in the HEADer of the article:
  473. **
  474. **    The header fields are either ignored, or formatted and put into the
  475. **    Text.
  476. */
  477.     (*targetClass.start_element)(target, HTML_H1 , 0, 0);
  478.     PUTS( "Newsgroups");
  479.     (*targetClass.end_element)(target, HTML_PRE);
  480.     p = line;
  481.     (*targetClass.start_element)(target, HTML_MENU , 0, 0);
  482.     while(!done){
  483.     char ch = *p++ = HTGetCharacter ();
  484.     if (ch==(char)EOF) {
  485.         abort_socket();    /* End of file, close socket */
  486.         return;        /* End of file on response */
  487.     }
  488.     if ((ch == LF) || (p == &line[LINE_LENGTH])) {
  489.         *p++=0;                /* Terminate the string */
  490.         if (TRACE) fprintf(stderr, "B %s", line);
  491.         (*targetClass.start_element)(target, HTML_LI , 0, 0);
  492.         if (line[0]=='.') {
  493.         if (line[1]<' ') {              /* End of article? */
  494.             done = YES;
  495.             break;
  496.         } else {            /* Line starts with dot */
  497.             PUTS( &line[1]);
  498.         }
  499.         } else {
  500.  
  501. /*    Normal lines are scanned for references to newsgroups.
  502. */
  503.         char group[LINE_LENGTH];
  504.         int first, last;
  505.         char postable;
  506.         if (sscanf(line, "%s %d %d %c", group, &first, &last, &postable)==4)
  507.             write_anchor(line, group);
  508.         else
  509.             PUTS(line);
  510.         } /* if not dot */
  511.         p = line;            /* Restart at beginning */
  512.     } /* if end of line */
  513.     } /* Loop over characters */
  514.     (*targetClass.end_element)(target, HTML_MENU);
  515. }
  516.  
  517.  
  518. /*    Read in a Newsgroup
  519. **    -------------------
  520. **    Unfortunately, we have to ask for each article one by one if we
  521. **    want more than one field.
  522. **
  523. */
  524. PRIVATE void read_group ARGS3(
  525.   CONST char *,groupName,
  526.   int,first_required,
  527.   int,last_required
  528. )
  529. {
  530.     char line[LINE_LENGTH+1];
  531.     char author[LINE_LENGTH+1];
  532.     char subject[LINE_LENGTH+1];
  533.     char *p;
  534.     BOOL done;
  535.  
  536.     char buffer[LINE_LENGTH];
  537.     char *reference=0;            /* Href for article */
  538.     int art;                /* Article number WITHIN GROUP */
  539.     int status, count, first, last;    /* Response fields */
  540.                     /* count is only an upper limit */
  541.  
  542.     sscanf(response_text, " %d %d %d %d", &status, &count, &first, &last);
  543.     if(TRACE) printf("Newsgroup status=%d, count=%d, (%d-%d) required:(%d-%d)\n",
  544.             status, count, first, last, first_required, last_required);
  545.     if (last==0) {
  546.     PUTS( "\nNo articles in this group.\n");
  547.     return;
  548.     }
  549.  
  550.     if (first_required<first) first_required = first;        /* clip */
  551.     if ((last_required==0) || (last_required > last)) last_required = last;
  552.  
  553.     if (last_required<=first_required) {
  554.     PUTS( "\nNo articles in this range.\n");
  555.     return;
  556.     }
  557.  
  558.     if (last_required-first_required+1 > MAX_CHUNK) {    /* Trim this block */
  559.     first_required = last_required-CHUNK_SIZE+1;
  560.     }
  561.     if (TRACE) printf (
  562.     "    Chunk will be (%d-%d)\n", first_required, last_required);
  563.  
  564. /*    Set window title
  565. */
  566.     sprintf(buffer, "Newsgroup %s,  Articles %d-%d",
  567.         groupName, first_required, last_required);
  568.     START(HTML_TITLE);
  569.     PUTS(buffer);
  570.     END(HTML_TITLE);
  571.  
  572. /*    Link to earlier articles
  573. */
  574.     if (first_required>first) {
  575.     int before;            /* Start of one before */
  576.     if (first_required-MAX_CHUNK <= first) before = first;
  577.     else before = first_required-CHUNK_SIZE;
  578.     sprintf(buffer, "%s/%d-%d", groupName, before, first_required-1);
  579.     if (TRACE) fprintf(stderr, "    Block before is %s\n", buffer);
  580.     PUTS( " (");
  581.     start_anchor(buffer);
  582.     PUTS("Earlier articles");
  583.     PUTS("</A>");
  584.     PUTS( "...)\n");
  585.     }
  586.  
  587.     done = NO;
  588.  
  589. /*    Read newsgroup using individual fields:
  590. */
  591.     if (!done) {
  592.     if (first==first_required && last==last_required)
  593.         PUTS("\nAll available articles in ");
  594.     else PUTS( "\nArticles in ");
  595.     PUTS(groupName);
  596.     START(HTML_MENU);
  597.     for(art=first_required; art<=last_required; art++) {
  598.  
  599.         sprintf(buffer, "HEAD %d%c%c", art, CR, LF);
  600.         status = response(buffer);
  601.  
  602.         if (status == 221) {    /* Head follows - parse it:*/
  603.  
  604.         p = line;                /* Write pointer */
  605.         done = NO;
  606.         while(!done){
  607.             char ch = *p++ = HTGetCharacter ();
  608.             if (ch==(char)EOF) {
  609.             abort_socket(); /* End of file, close socket */
  610.             return;     /* End of file on response */
  611.             }
  612.             if ((ch == LF)
  613.             || (p == &line[LINE_LENGTH]) ) {
  614.  
  615.             *--p=0;     /* Terminate  & chop LF*/
  616.             p = line;        /* Restart at beginning */
  617.             if (TRACE) fprintf(stderr, "G %s\n", line);
  618.             switch(line[0]) {
  619.  
  620.             case '.':
  621.                 done = (line[1]<' ');       /* End of article? */
  622.                 break;
  623.  
  624.             case 'S':
  625.             case 's':
  626.                 if (match(line, "SUBJECT:"))
  627.                 strcpy(subject, line+9);/* Save subject */
  628.                 break;
  629.  
  630.             case 'M':
  631.             case 'm':
  632.                 if (match(line, "MESSAGE-ID:")) {
  633.                 char * addr = HTStrip(line+11) +1; /* Chop < */
  634.                 addr[strlen(addr)-1]=0;     /* Chop > */
  635.                 StrAllocCopy(reference, addr);
  636.                 }
  637.                 break;
  638.  
  639.             case 'f':
  640.             case 'F':
  641.                 if (match(line, "FROM:")) {
  642.                 char * p;
  643.                 char *aname = author_name(strchr(line,':')+1);
  644.                 if (aname && *aname)
  645.                   {
  646.                     strcpy(author, aname);
  647.                     p = author + strlen(author) - 1;
  648.                     if (*p==LF) *p = 0; /* Chop off newline */
  649.                   }
  650.                 else
  651.                   {
  652.                     strcpy(author, "Unknown");
  653.                   }
  654.                   }
  655.                 break;
  656.  
  657.             } /* end switch on first character */
  658.             } /* if end of line */
  659.         } /* Loop over characters */
  660.  
  661.         START(HTML_LI);
  662.         sprintf(buffer, "\"%s\" - %s", subject, author);
  663.         if (reference) {
  664.             write_anchor(buffer, reference);
  665.             free(reference);
  666.             reference=0;
  667.         } else {
  668.             PUTS(buffer);
  669.         }
  670.  
  671.  
  672. /*     indicate progress!   @@@@@@
  673. */
  674.  
  675.         } /* If good response */
  676.     } /* Loop over article */
  677.     } /* If read headers */
  678.     END(HTML_MENU);
  679.     START(HTML_P);
  680.  
  681. /*    Link to later articles
  682. */
  683.     if (last_required<last) {
  684.     int after;            /* End of article after */
  685.     after = last_required+CHUNK_SIZE;
  686.     if (after==last) sprintf(buffer, "news:%s", groupName); /* original group */
  687.     else sprintf(buffer, "news:%s/%d-%d", groupName, last_required+1, after);
  688.     if (TRACE) fprintf(stderr, "    Block after is %s\n", buffer);
  689.     PUTS( "(");
  690.     start_anchor(buffer);
  691.     PUTS( "Later articles");
  692.     PUTS("</A>");
  693.     PUTS( "...)\n");
  694.     }
  695. }
  696.  
  697.  
  698. /*        Load by name                    HTLoadNews
  699. **        ============
  700. */
  701. PUBLIC int HTLoadNews ARGS4(
  702.     CONST char *,        arg,
  703.     HTParentAnchor *,    anAnchor,
  704.     HTFormat,        format_out,
  705.     HTStream*,        stream)
  706. {
  707.   char command[257];            /* The whole command */
  708.   char groupName[GROUP_NAME_LENGTH];    /* Just the group name */
  709.   int status;                /* tcp return */
  710.   int retries;            /* A count of how hard we have tried */
  711.   BOOL group_wanted;            /* Flag: group was asked for, not article */
  712.   BOOL list_wanted;            /* Flag: group was asked for, not article */
  713.   int first, last;            /* First and last articles asked for */
  714.  
  715.   diagnostic = (format_out == WWW_SOURCE);    /* set global flag */
  716.  
  717.   if (TRACE)
  718.     fprintf(stderr, "HTNews: Looking for %s\n", arg);
  719.  
  720.   if (!initialized)
  721.     initialized = initialize();
  722.   if (!initialized)
  723.     {
  724.       HTProgress ("Could not set up news connection.");
  725.       return HT_NOT_LOADED;    /* FAIL */
  726.     }
  727.  
  728.   {
  729.     CONST char * p1=arg;
  730.  
  731.     /*    We will ask for the document, omitting the host name & anchor.
  732.      **
  733.      ** Syntax of address is
  734.      **     xxx@yyy         Article
  735.      **     <xxx@yyy>        Same article
  736.      **     xxxxx            News group (no "@")
  737.      **     group/n1-n2        Articles n1 to n2 in group
  738.      */
  739.     group_wanted = (strchr(arg, '@')==0) && (strchr(arg, '*')==0);
  740.     list_wanted  = (strchr(arg, '@')==0) && (strchr(arg, '*')!=0);
  741.  
  742.     /* p1 = HTParse(arg, "", PARSE_PATH | PARSE_PUNCTUATION); */
  743.     /* Don't use HTParse because news: access doesn't follow traditional
  744.        rules. For instance, if the article reference contains a '#',
  745.        the rest of it is lost -- JFG 10/7/92, from a bug report */
  746.     if (!strncasecomp (arg, "news:", 5))
  747.       p1 = arg + 5;  /* Skip "news:" prefix */
  748.     if (list_wanted)
  749.       {
  750.     strcpy(command, "LIST ");
  751.       }
  752.     else if (group_wanted)
  753.       {
  754.     char * slash = strchr(p1, '/');
  755.     strcpy(command, "GROUP ");
  756.     first = 0;
  757.     last = 0;
  758.     if (slash)
  759.       {
  760.         *slash = 0;
  761.         strcpy(groupName, p1);
  762.         *slash = '/';
  763.         (void) sscanf(slash+1, "%d-%d", &first, &last);
  764.       }
  765.     else
  766.       {
  767.         strcpy(groupName, p1);
  768.       }
  769.     strcat(command, groupName);
  770.       }
  771.     else
  772.       {
  773.     strcpy(command, "ARTICLE ");
  774.     if (strchr(p1, '<')==0)
  775.       strcat(command,"<");
  776.     strcat(command, p1);
  777.     if (strchr(p1, '>')==0)
  778.       strcat(command,">");
  779.       }
  780.  
  781.     {
  782.       char * p = command + strlen(command);
  783.       *p++ = CR;        /* Macros to be correct on Mac */
  784.       *p++ = LF;
  785.       *p++ = 0;
  786.     }
  787.   } /* scope of p1 */
  788.  
  789.   if (!*arg)
  790.     {
  791.       HTProgress ("Could not load data.");
  792.       return HT_NOT_LOADED;            /* Ignore if no name */
  793.     }
  794.  
  795.   /*    Make a hypertext object with an anchor list. */
  796.   node_anchor = anAnchor;
  797.   target = HTML_new(anAnchor, format_out, stream);
  798.   targetClass = *target->isa;    /* Copy routine entry points */
  799.  
  800.  
  801.   /*    Now, let's get a stream setup up from the NewsHost: */
  802.   for (retries=0; retries<2; retries++)
  803.     {
  804.       target = HTML_new(anAnchor, format_out, stream);
  805.       targetClass = *target->isa;    /* Copy routine entry points */
  806.       if (s < 0)
  807.     {
  808.       /* CONNECTING to news host */
  809.       char url[1024];
  810.       sprintf (url, "lose://%s/", HTNewsHost);
  811.       if (TRACE)
  812.         fprintf (stderr, "News: doing HTDoConnect on '%s'\n", url);
  813.       status = HTDoConnect (url, "NNTP", NEWS_PORT, &s);
  814.       if (TRACE)
  815.         fprintf (stderr, "News: Done DoConnect; status %d\n", status);
  816.       if (status == HT_INTERRUPTED)
  817.         {
  818.           /* Interrupt cleanly. */
  819.           fprintf (stderr,
  820.                "News: Interrupted on connect; recovering cleanly.\n");
  821.           HTProgress ("Connection interrupted.");
  822.  
  823.           (*targetClass.handle_interrupt)(target);
  824.  
  825.           return HT_INTERRUPTED;
  826.         }
  827.       if (status < 0)
  828.         {
  829.           char message[256];
  830.           NETCLOSE(s);
  831.           s = -1;
  832.           if (TRACE)
  833.         fprintf(stderr, "HTNews: Unable to connect to news host.\n");
  834.           if (retries<=1)
  835.         {
  836.           /* Since we reallocate on each retry, free here. */
  837.           (*targetClass.end_document)(target);
  838.           (*targetClass.free)(target);
  839.           continue;
  840.         }
  841.           HTProgress ("Could not access news host.");
  842.           sprintf(message,
  843.               "\nCould not access news host %s.  Try setting environment variable <code>NNTPSERVER</code> to the name of your news host, and restart Mosaic.",
  844.               HTNewsHost);
  845.  
  846.           PUTS(message);
  847.           (*targetClass.end_document)(target);
  848.           (*targetClass.free)(target);
  849.           return HT_LOADED;
  850.         }
  851.       else
  852.         {
  853.           if (TRACE) fprintf(stderr, "HTNews: Connected to news host %s.\n",
  854.                  HTNewsHost);
  855.           HTInitInput(s);        /* set up buffering */
  856.           if ((response(NULL) / 100) !=2)
  857.         {
  858.           NETCLOSE(s);
  859.           s = -1;
  860.           /* Couldn't get it. */
  861.           START(HTML_TITLE);
  862.           PUTS("Could Not Retrieve Information");
  863.           END(HTML_TITLE);
  864.           PUTS("Sorry, could not retrieve information.");
  865.           (*targetClass.end_document)(target);
  866.           (*targetClass.free)(target);
  867.           return HT_LOADED;
  868.         }
  869.         }
  870.     } /* If needed opening */
  871.  
  872.       status = response(command);
  873.       if (TRACE)
  874.     fprintf (stderr, "News: Sent '%s', status %d\n", command, status);
  875.       if (status < 0)
  876.     break;
  877.       if ((status/ 100) !=2)
  878.     {
  879.       PUTS(response_text);
  880.       (*targetClass.end_document)(target);
  881.       (*targetClass.free)(target);
  882.       NETCLOSE(s);
  883.       s = -1;
  884.       continue;    /* Try again */
  885.     }
  886.  
  887.       /*    Load a group, article, etc
  888.        */
  889.  
  890.       if (list_wanted)
  891.     read_list();
  892.       else if (group_wanted)
  893.     read_group(groupName, first, last);
  894.       else
  895.     read_article();
  896.  
  897.       (*targetClass.end_document)(target);
  898.       (*targetClass.free)(target);
  899.       return HT_LOADED;
  900.  
  901.     } /* Retry loop */
  902.  
  903. #if 0
  904.   PUTS("Sorry, could not load requested news.\n");
  905.   (*targetClass.end_document)(target);
  906. #endif
  907.  
  908.   return HT_LOADED;
  909. }
  910.  
  911. PUBLIC HTProtocol HTNews = { "news", HTLoadNews, NULL };
  912.